/*
 * @Author       : Xu Xiaokang
 * @Email        :
 * @Date         : 2025-04-24 22:01:07
 * @LastEditors  : Xu Xiaokang
 * @LastEditTime : 2025-05-07 10:39:23
 * @Filename     :
 * @Description  :
*/

module myTDPRAM_Top_tb();


//++ 仿真时间尺度 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
timeunit 1ns;
timeprecision 1ps;
//-- 仿真时间尺度 ------------------------------------------------------------


//++ 底层模块实例化 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam RAM_STYLE = "block"; //* RAM类型, 可选"block"(默认), "distributed"
localparam DATA_WIDTH = 8; //* 数据位宽, 可选1, 2, 3, ..., 默认为8
localparam ADDR_WIDTH = 6; //* RAM地址位宽, 对应RAM深度, 可选1, 2, 3, ..., 默认为6, 对应深度2**6=64
localparam OPERATING_MODE_A = "WF"; //* 可选"Write First"(默认), "Read First", "No Change"
localparam OPERATING_MODE_B = "WF"; //* 可选"Write First"(默认), "Read First", "No Change"
localparam [0:0] USE_ENA = 0; //* 启用ENA信号
localparam [0:0] USE_ENB = 0; //* 启用ENB信号
localparam [1:0] OUTPUT_REG_NUM = 0; //* 可选0(默认), 1, 2
localparam INIT_FILE = ""; //* 初始化文件名，空(默认)表示不初始化，示例目录C:\_myJGY\ram_init.coe

logic clka;
logic ena;
logic wea;
logic [ADDR_WIDTH-1:0] addra;
logic [DATA_WIDTH-1:0] dina;
logic [DATA_WIDTH-1:0] douta;
logic [DATA_WIDTH-1:0] vivado_douta;
logic clkb;
logic enb;
logic web;
logic [ADDR_WIDTH-1:0] addrb;
logic [DATA_WIDTH-1:0] dinb;
logic [DATA_WIDTH-1:0] doutb;
logic [DATA_WIDTH-1:0] vivado_doutb;

myTDPRAM_Top #(
  .RAM_STYLE        (RAM_STYLE       ),
  .DATA_WIDTH       (DATA_WIDTH      ),
  .ADDR_WIDTH       (ADDR_WIDTH      ),
  .OPERATING_MODE_A (OPERATING_MODE_A),
  .OPERATING_MODE_B (OPERATING_MODE_B),
  .USE_ENA          (USE_ENA         ),
  .USE_ENB          (USE_ENB         ),
  .OUTPUT_REG_NUM   (OUTPUT_REG_NUM  ),
  .INIT_FILE        (INIT_FILE       )
) myTDPRAM_Top_inst(.*);
//-- 底层模块实例化 ------------------------------------------------------------


//++ 随机种子 与 种子数组 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
/*
* 通过调用C语言函数获取系统时间作为种子,
* 重启仿真种子会变化
*/
import "DPI-C" function longint get_system_time();
longint timestamp_us;
int timestamp_seed;
event timestamp_seed_ready;
initial begin
  timestamp_us = get_system_time();
  $display("timestamp_us = ", timestamp_us);
  timestamp_seed = int'(timestamp_us ^ (timestamp_us >> 32)); // 高低位异或
  $display("timestamp_seed = ", timestamp_seed);
  $display("timestamp_seed initial success!!!!!!!!!!");
  -> timestamp_seed_ready;
end

int seeds [20];
event seeds_ready;
initial begin
  wait(timestamp_seed_ready.triggered); //* 等待系统时间种子初始化完成
  $srandom(timestamp_seed);
  foreach (seeds[i]) begin
    seeds[i] = $urandom();  //* 使用 $urandom() 初始化 seeds 数组
  end
  for (int i=0; i<20; i++) begin
    $display("seeds[%0d] = 0x%08x", i, seeds[i]);
  end
  $display("seeds initial success!!!!!!!!!!");
  -> seeds_ready; //* 种子数组初始化完成
end
//-- 随机种子 与 种子数组 ------------------------------------------------------------


//++ 生成时钟 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
event clka_is_ok;
int unsigned CLKAT;
initial begin
  clka = 0;
  wait(seeds_ready.triggered); //* 等待种子数组初始化完成
  $srandom(seeds[0]); //* 指定此initial线程的初始种子;
  CLKAT = $urandom_range(2, 5);
  -> clka_is_ok;
  forever #(CLKAT / 2) clka = ~clka;
end

event clkb_is_ok;
int unsigned CLKBT;
initial begin
  clkb = 0;
  wait(seeds_ready.triggered); //* 等待种子数组初始化完成
  $srandom(seeds[1]); //* 指定此initial线程的初始种子;
  CLKBT = $urandom_range(2, 5);
  -> clkb_is_ok;
  forever #(CLKBT / 2) clkb = ~clkb;
end
//-- 生成时钟 ------------------------------------------------------------


//++ 仿真过程 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam REPEAT_NUM = 10000;

logic a_sim_end = 0;
initial begin
  wait(seeds_ready.triggered); //* 等待种子数组初始化完成
  $srandom(seeds[2]); //* 指定此initial线程的初始种子;
  wait(clka_is_ok.triggered);
  repeat (REPEAT_NUM) begin
    @(posedge clka);
    #(CLKAT * 0.1);
    wea = $urandom_range(0, 1);  // 随机写使能
    addra = $urandom_range(0, 2**ADDR_WIDTH-1); // 随机地址（0~63）
    dina = $urandom_range(0, 2**DATA_WIDTH-1); // 随机数据
    #(CLKAT * 0.9);
  end
  a_sim_end = 1;
end

logic b_sim_end = 0;
initial begin
  wait(seeds_ready.triggered); //* 等待种子数组初始化完成
  $srandom(seeds[3]); //* 指定此initial线程的初始种子
  wait(clkb_is_ok.triggered);
  repeat (REPEAT_NUM) begin
    @(posedge clkb);
    #(CLKBT * 0.1);
    web = $urandom_range(0, 1);
    addrb = $urandom_range(0, 2**ADDR_WIDTH-1);
    dinb = $urandom_range(0, 2**DATA_WIDTH-1);
    #(CLKBT * 0.9);
  end
  b_sim_end = 1;
end

//* 仿真结束条件
initial begin
  wait(a_sim_end == 1 && b_sim_end == 1);
  $display("======================== OK OK OK OK ========================");
  $finish;
end
//++ 仿真过程 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


//++ 冲突检测 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire true_ena = (USE_ENA == 0) ? 1'b1 : ena;
wire true_enb = (USE_ENB == 0) ? 1'b1 : enb;

/*
* 冲突分为两种: 1.写-写冲突；2.写-读冲突。
*/
logic collision_detected; //* 发生冲突则置高
logic write_collision; //* A与B同时写冲突
logic a_wr_b_rd_collision; //* A写B读冲突
logic a_rd_b_wr_collision; //* A读B写冲突
initial begin
  forever @(posedge clka, posedge clkb) begin
    if (true_ena && true_enb) begin
      if (addra == addrb && wea && web) begin
        write_collision = 1;
        $display("write_collision!!!, addra = %h, dina = %h, addrb = %h, dinb = %h",
                addra, dina, addrb, dinb);
      end else
        write_collision = 0;
      if (addra == addrb && wea) begin
        a_wr_b_rd_collision = 1;
        $display("a_wr_b_rd_collision!!!, addra = %h, dina = %h, addrb = %h, dinb = %h",
                addra, dina, addrb, dinb);
      end else
        a_wr_b_rd_collision = 0;
      if (addra == addrb && web) begin
        a_rd_b_wr_collision = 1;
        $display("a_rd_b_wr_collision!!!, addra = %h, dina = %h, addrb = %h, dinb = %h",
                addra, dina, addrb, dinb);
      end else
        a_rd_b_wr_collision = 0;
    end
  end
end

assign collision_detected = write_collision || a_wr_b_rd_collision || a_rd_b_wr_collision;
//-- 冲突检测 ------------------------------------------------------------


//++ 判断自编IP与Vivado IP输出是否一致 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
initial begin
  wait(clka_is_ok.triggered);
  #(CLKAT * 3.3);
  //* 在 clka 的上升沿判断条件
  forever @(posedge clka) begin
    #(CLKAT / 2 - 0.01);
    if (~collision_detected)
      assert (douta == vivado_douta) //* 断言两者输出一致
      else   begin
        $display("A Error!!!!!!!!!!!!!!");
        #0.5;
        $stop; // 停止仿真
      end
  end
end


initial begin
  wait(clkb_is_ok.triggered);
  #(CLKBT * 3.3);
  //* 在 clkb 的上升沿判断条件
  forever @(posedge clkb) begin
    #(CLKBT / 2 - 0.01);
    if (~collision_detected)
      assert (doutb == vivado_doutb) //* 断言两者输出一致
      else   begin
        $display("B Error!!!!!!!!!!!!!!");
        #0.5;
        $stop; // 停止仿真
      end
  end
end
//-- 判断自编IP与Vivado IP输出是否一致 ------------------------------------------------------------


endmodule